//命令行解释器模拟实现
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>


#define NUM 1024
#define OPT_NUM 64

#define NONE_REDIR    0  //没有重定向
#define INPUT_REDIR   1  //输入重定向
#define OUTPUT_REDIR  2
#define APPEND_REDIR  3

//空格过滤器函数
#define trimSpace(start) do{\
  while(isspace(*start)) ++start;\
}while(0)

char lineCommand[NUM];//存放输入指令
char* myargv[OPT_NUM];
int lastCode=0;
int lastSig=0;

int redirType=NONE_REDIR;//重定向状态
char* redirfile =NULL;

//ls -a > mytext.txt
void commandCheck(char* commands)
{
    assert(commands);
    char* start=commands;
    char* end=commands+strlen(commands);
    while(start <end)
    {
        if(*start=='>')
        {
            *start='\0';
            start++;
            if(*start=='>')
            {
              //"ls-a -l >> myfile.txt"
                redirType=APPEND_REDIR;
                start++;
            }
            else
            {
              //"ls-a -l > myfile.txt"
                redirType=OUTPUT_REDIR;
            }
            trimSpace(start);
            redirfile=start;
            break;
        }
        else if (*start=='<')
        {
            //"cat <    test.txt"
            *start='\0';
            start++;
            trimSpace(start);

            redirType=INPUT_REDIR;
            redirfile=start;
            break;
        }
        else{
          start++;
        }
    }
}

int main()
{
  while(1)
  {
     redirType = NONE_REDIR;
     redirfile = NULL;
     errno = 0;

      //输出提示符
      printf("用户名@主机名：当前路径# ");
      fflush(stdout);

      //获取用户输入，以\n结束
      char* s=fgets(lineCommand,sizeof(lineCommand)-1,stdin);
      assert(s);
      (void)s;
      //由于输入以\n结束，此处需要清理
      lineCommand[strlen(lineCommand)-1]=0;
      //printf("test==%s\n",lineCommand);

      ////重定向
      //ls -a >myfile.c 
      //ls -a >>myfile.c 
      //cat <myfile.c 
     commandCheck(lineCommand); 
      
      
      
      //对输入的字符串切割，换成一条条指令
      //strtok如果找不到会返回NULL
      //ls -a -l -i ->"ls" "-a" "-l"
      myargv[0]=strtok(lineCommand," ");
      int i=1;
      if(myargv[0]!= NULL && strcmp(myargv[0],"ls")==0)
      {
        myargv[i++]=(char*)"--color=auto";
      }
      

      //循环切割，直到没有“ ”，返回NULL
      while(myargv[i++]=strtok(NULL," "));
      

      //cd ..命令,不需要创建子进程，让shell自己去执行调用系统接口
      //内建/内置命令
      if(myargv[0]!= NULL && strcmp(myargv[0],"cd")==0)
      {
        if(myargv[1]!=NULL)
        {
          //切换进程的工作目录（当前路径）,这是父进程调用的，不需要创建子进程
            chdir(myargv[1]);
        }
         continue;
      }
      //echo $?
      if(myargv[0]!= NULL && myargv[1] != NULL && strcmp(myargv[0],"echo")==0)
      {
          if(strcmp(myargv[1],"$?")==0)
          {
              printf("%d,%d\n",lastCode,lastSig);
          }
          else
          {
            printf("%s\n",myargv[1]);

          }
          continue;
      }
   
#ifdef DEBUG
        for(int i = 0 ; myargv[i]; i++)
        {
            printf("myargv[%d]: %s\n", i, myargv[i]);
        }
#endif
        //执行命令
        pid_t id=fork();
        assert(id != -1);
        if(id==0)
        {
          //命令是子进程执行的，真正重定向的工作是子进程进行的
          //父进程给子进程提供如何重定向的信息
          //由于进程具有独立性，这里重定向并不影响父进程
          switch(redirType)
          {
            case NONE_REDIR:
              break;
            case INPUT_REDIR:
              {
                int fd=open(redirfile,O_RDONLY);
                if(fd<0)
                {
                    perror("open");
                    exit(errno);
                }
                //已经打开重定向的文件,开始替换
                dup2(fd,0);
              }
              break;
            case OUTPUT_REDIR:
            case APPEND_REDIR:
              {
                umask(0);
                int flags=O_WRONLY|O_CREAT;
                if(redirType==APPEND_REDIR) flags |= O_APPEND;
                else flags |= O_TRUNC;
                
                int fd=open(redirfile,flags,0666);
                if(fd<0)
                {
                    perror("open");
                    exit(errno);
                }
                dup2(fd,1);

              }
              break;
            default:
              printf("bug？？？\n");
              break;
          }
          
          
          execvp(myargv[0],myargv);
          exit(1);//只要返回，必出错
        }
        int status=0;
        pid_t ret=waitpid(id,&status,0);
        assert(ret>0);
     //   printf("pid_t==%d",ret);
        (void)ret;
        lastCode=((status>>8)& 0xFF);
        lastSig=(status & 0x7F);
     }
}

